package co.edu.unal.geditor.model;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.UndoManager;
import javax.swing.undo.UndoableEdit;
import javax.swing.undo.UndoableEditSupport;

import co.edu.unal.geditor.controller.App;
import co.edu.unal.geditor.controller.action.undoable.UndoableCreate;
import co.edu.unal.geditor.controller.action.undoable.UndoableDelete;
import co.edu.unal.geditor.controller.action.undoable.UndoableMove;
import co.edu.unal.geditor.controller.action.undoable.UndoableResize;
import co.edu.unal.geditor.model.DrawingListener.DrawingEvent;
import co.edu.unal.geditor.view.MainFrame;

public class Drawing implements Serializable {
	private static final long serialVersionUID = 1L;

	public Drawing() {
		// build an empty list of objects
		m_elements = new ArrayList<GraphElement>();

		// build an empty list of listeners
		m_listeners = new ArrayList<DrawingListener>();

		// new in v0.8
		m_undoManager = new UndoManager();
		m_undoManager.setLimit(100);

		m_undoSupport = new UndoableEditSupport();
		m_undoSupport.addUndoableEditListener(new UndoAdapter());
		m_colorBackground = Color.white;
		// new in v0.8
	}

	// new in v0.8
	public void init() {
		refreshUndoRedo();
	}

	public void setFillingColor(Color newFillingColor) {
		ClosedGraphElement.setDefaultFillingColor(newFillingColor);
		for (GraphElement ge : m_elements) {
			if (ge.getBoundBox().isSelected()) {
				ge.setFillColor(newFillingColor);
			}
		}
		setModified(true);
	}

	public void setLinesColor(Color newLinesColor) {
		GraphElement.setDefaultLinesColor(newLinesColor);
		for (GraphElement ge : m_elements) {
			if (ge.getBoundBox().isSelected()) {
				ge.setLinesColor(newLinesColor);
			}
		}
		setModified(true);
	}

	public void addUndoable(AbstractUndoableEdit e) {
		m_undoSupport.postEdit(e);
	}

	// new in v0.8

	public void paint(Graphics g) {
		for (GraphElement ge : m_elements) {
			ge.paint(g);
		}
	}

	// the drawing is responsible for knowing how to
	// store itself (collaboration: GraphElement)
	public boolean save(ObjectOutputStream oos) {
		boolean bSavedOk = true;

		try {
			// write number of elements first (Java auto-boxing feature)
			oos.writeObject(m_elements.size());

			// then the elements
			for (GraphElement ge : m_elements) {
				// for this to work, GraphElement has to implement the
				// Serializable interface
				// (a tagged interface: no methods), and *all* (not transient)
				// GraphElement
				// attributes have to implement the Serializable interface
				// (recursively)
				oos.writeObject(ge);
			}
			oos.writeObject(m_colorBackground);
			oos.writeObject(GraphElement.getDefaultLinesColor());
			oos.writeObject(ClosedGraphElement.getDefaultFillingColor());
		} catch (IOException exc) {
			// debug msg
			exc.printStackTrace();
			bSavedOk = false;
		}

		if (bSavedOk) {
			// notifies all listeners
			setModified(false);
		}

		return bSavedOk;
	}

	public void clear() {
		assert (isModified() == false);

		m_elements.clear();
		setcolorBackground(Color.white);
		setModified(true);
		clearUndoManager();
	}

	public void clearUndoManager() {
		m_undoManager.discardAllEdits();
		refreshUndoRedo();
	}

	public void elementMoved(GraphElement movedElement) {
		m_undoSupport.postEdit(new UndoableMove(movedElement));
		removeElementAt(m_elements.indexOf(movedElement));
		insertElementAt(movedElement, (m_elements.size())); // Remueve y añade
		// el objeto de
		// último para que
		// se dibuje de
		// último y así
		// aparezca en
		// frente de todos.
		// Es decir que
		// cuando se esten
		// realizando
		// acciones sobre
		// este objeto,
		// quede de primero
		// en el dibujo

	}

	public void elementResized(GraphElement resizedElement) {
		m_undoSupport.postEdit(new UndoableResize(resizedElement));
		removeElementAt(m_elements.indexOf(resizedElement));
		insertElementAt(resizedElement, (m_elements.size())); // Remueve y añade
		// el objeto de
		// último para
		// que se dibuje
		// de último y
		// así aparezca
		// en frente de
		// todos. Es
		// decir que
		// cuando se
		// esten
		// realizando
		// acciones
		// sobre este
		// objeto, quede
		// de primero en
		// el dibujo

	}

	// the drawing is responsible for knowing how to
	// load itself (collaboration: GraphElement)
	// PRE-CONDITION: document saved
	// POST-CONDITION: contents changed
	public boolean open(ObjectInputStream ois) {
		assert (isModified() == false);

		// remove all elements
		clear();

		boolean bLoadedOk = true;

		try {
			// read number of elements (Java auto-unboxing feature)
			int n = (Integer) ois.readObject();

			for (int i = 0; i < n; i++) {
				GraphElement ge = (GraphElement) ois.readObject();
				if (ge != null) // sanity check
				{
					// element's view not stored
					ge.setView();
					ge.getBoundBox().setView();
					// addElement( ge ); we don't want to notify listeners this
					// time
					m_elements.add(ge);
				} else {
					// stop loading
					bLoadedOk = false;
					break;
				}
			}
			setcolorBackground((Color) ois.readObject());
			setLinesColor((Color) ois.readObject());
			setFillingColor((Color) ois.readObject());
		} catch (Exception exc) {
			// debug msg
			exc.printStackTrace();
			bLoadedOk = false;
		}

		if (bLoadedOk) {
			// notify listeners
			setModified(false);
		}

		return bLoadedOk;
	}

	public void select(BoundBox bb) {
		boolean bSomeSelected = false;

		// BoundBox bb = new BoundBox( r );
		bb.normalize();

		for (GraphElement ge : m_elements) {
			if (ge.select(bb)) {
				bSomeSelected = true;
			}
		}

		if (bSomeSelected) {
			notifyListeners(DrawingEvent.SELECTION);
		}
	}

	public void mouseMoving(Point ptActualPos) {
		for (int i = m_elements.size() - 1; i >= 0; i--) {
			GraphElement ge = m_elements.get(i);
			if (ge.contain(ptActualPos)) {
				break;
			} else {

			}

		}
	}

	public void addListener(DrawingListener dl) {
		assert dl != null;

		if (!m_listeners.contains(dl)) {
			m_listeners.add(dl);
		}
	}

	// not used in thBoundBoxis app
	public void removeListener(DrawingListener dl) {
		assert dl != null;

		m_listeners.remove(dl);
	}

	// observer design pattern
	public void notifyListeners(DrawingEvent de) {
		assert de != null;

		for (DrawingListener dl : m_listeners) {
			dl.processDrawingEvent(de);
		}
	}

	// public ok
	public boolean isModified() {
		return m_bModified;
	}

	// private !
	private void setModified(boolean modified) {
		m_bModified = modified;

		if (m_bModified) {
			notifyListeners(DrawingEvent.MODIFIED);
		} else {
			notifyListeners(DrawingEvent.SAVED);
		}
	}

	// PRE-CONDITION: ge != null
	// POST-CONDITION: notified listeners that the document has changed
	public void addElement(GraphElement ge) {
		assert ge != null;

		// new in v0.8
		m_undoSupport.postEdit(new UndoableCreate(ge, m_elements.size()));
		// new in v0.8

		m_elements.add(ge);
		setModified(true);
	}

	public String getFileName() {
		return m_sFileName;
	}

	public void setFileName(String fileName) {
		m_sFileName = fileName;
	}

	// new in v0.8
	public void removeElementAt(int index) {
		ArrayList<GraphElement> newArray = new ArrayList<GraphElement>();
		m_elements.remove(index);
		for (int i = 0; i < m_elements.size(); i++) {
			if (m_elements.get(i) != null) {
				newArray.add(m_elements.get(i));
			}
		}
		m_elements = newArray;
		// document changed: notify listeners
		setModified(true);
	}

	// TODO: check index & array size
	public void insertElementAt(GraphElement ge, int index) {
		// new bigger list
		ArrayList<GraphElement> newArray = new ArrayList<GraphElement>(
				m_elements.size() + 1);

		// copy first index-1 elements
		for (int i = 0; i < index; i++) {
			newArray.add(m_elements.get(i));
		}

		// insert the element
		newArray.add(ge);

		// copy the rest
		for (int i = index; i < m_elements.size(); i++) {
			newArray.add(m_elements.get(i));
		}

		m_elements = newArray;

		// document changed: notify listeners
		setModified(true);
	}

	// new in v0.8

	// new in v0.8
	private class UndoAdapter implements UndoableEditListener {
		public void undoableEditHappened(UndoableEditEvent evt) {
			UndoableEdit edit = evt.getEdit();
			m_undoManager.addEdit(edit);
			refreshUndoRedo();
		}
	}

	public void undo() {
		m_undoManager.undo();
		setModified(true);
		refreshUndoRedo();
	}

	public void redo() {
		m_undoManager.redo();
		setModified(true);
		refreshUndoRedo();
	}

	public void refreshUndoRedo() {
		MainFrame mf = App.getInstance().getMainFrame();

		// refresh undo
		mf.setUndoText(m_undoManager.getUndoPresentationName());
		mf.setUndoEnabled(m_undoManager.canUndo());

		// refresh redo
		mf.setRedoText(m_undoManager.getRedoPresentationName());
		mf.setRedoEnabled(m_undoManager.canRedo());
	}

	public void deSelectAll() {
		for (GraphElement ge : m_elements) {
			ge.getBoundBox().setSelected(false);
		}

	}

	public void delete() {
		Map<GraphElement, String> deletedElements = new HashMap<GraphElement, String>();
		ArrayList<GraphElement> selectedElements = selectedElements();
		for (GraphElement ge : selectedElements) {
			deletedElements.put(ge, Integer.toString(m_elements.indexOf(ge)));
			removeElementAt(m_elements.indexOf(ge));

		}
		m_undoSupport.postEdit(new UndoableDelete(deletedElements,
				selectedElements));

	}
	public ArrayList<GraphElement> selectedElements(){
		ArrayList<GraphElement> selectedElements = new ArrayList<GraphElement>();
		for (GraphElement ge : m_elements) {
			if (ge.getBoundBox().isSelected()) {
				selectedElements.add(ge);
			}
		}
		return selectedElements;
	}

	public int getElementOrder(GraphElement ge) {
		assert ge != null;
		return m_elements.indexOf(ge);
	}

	public Color getcolorBackground() {
		return m_colorBackground;
	}

	public void setcolorBackground(Color mColorBackground) {

		m_colorBackground = mColorBackground;
		App.getInstance().getCanvas().setBackground(m_colorBackground);

	}

	UndoManager m_undoManager;
	UndoableEditSupport m_undoSupport;

	// list of elements
	private ArrayList<GraphElement> m_elements;

	// list of listeners
	private ArrayList<DrawingListener> m_listeners;

	// document status
	private boolean m_bModified;

	// document file name
	private String m_sFileName;

	// drawing file extension
	public static final String EXT = ".gef";

	private Color m_colorBackground;

}
